#undef BLENDSTRETCH
#undef BLENDPIXELSTRETCH
#undef BLENDBORDERSTRETCH

#define BLENDSTRETCH MAKE_BLEND(BLEND_MODE, PIXEL_BITS, StretchBicubic, )
#define BLENDPIXELSTRETCH MAKE_BLENDPIXELSTRETCH(BLEND_MODE, PIXEL_BITS, Bicubic, )
#define BLENDBORDERSTRETCH MAKE_BLENDBORDERSTRETCH(BLEND_MODE, PIXEL_BITS, Bicubic, )

#ifndef BLENDALPHA
__forceinline void BLENDPIXELSTRETCH(const PBITMAPEX lpbBitmap, const int x16, const int y16, LPXARGB result)
#else
__forceinline void BLENDPIXELSTRETCH(const PBITMAPEX lpbBitmap, const int x16, const int y16, LPXARGB result, DWORD alpha)
#endif
{
    DWORD u8 = (BYTE)(x16 >> 8);
    DWORD v8 = (BYTE)(y16 >> 8);
    LPBYTE pPixel = (LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * ((y16 >> 16) - 1) + ((x16 >> 16) - 1) * PIXEL_BYTES;

    DWORD au8[4], av8[4];
    au8[0] = StretchTable[(1 << 8) + u8];
    au8[1] = StretchTable[u8];
    au8[2] = StretchTable[(1 << 8) - u8];
    au8[3] = StretchTable[(2 << 8) - u8];
    av8[0] = StretchTable[(1 << 8) + v8];
    av8[1] = StretchTable[v8];
    av8[2] = StretchTable[(1 << 8) - v8];
    av8[3] = StretchTable[(2 << 8) - v8];

    int sR = 0, sG = 0, sB = 0, sA = 0;
    xARGB pixel[4];
    for (int i = 0; i < 4; ++i)
    {
        pixel[0] = GETPIXEL(lpbBitmap, pPixel);
        pixel[1] = GETPIXEL(lpbBitmap, pPixel + PIXEL_BYTES);
        pixel[2] = GETPIXEL(lpbBitmap, pPixel + PIXEL_BYTES * 2);
        pixel[3] = GETPIXEL(lpbBitmap, pPixel + PIXEL_BYTES * 3);
        int aA = au8[0] * pixel[0].alpha + au8[1] * pixel[1].alpha + au8[2] * pixel[2].alpha + au8[3] * pixel[3].alpha;
        int aR = au8[0] * pixel[0].red + au8[1] * pixel[1].red + au8[2] * pixel[2].red + au8[3] * pixel[3].red;
        int aG = au8[0] * pixel[0].green + au8[1] * pixel[1].green + au8[2] * pixel[2].green + au8[3] * pixel[3].green;
        int aB = au8[0] * pixel[0].blue + au8[1] * pixel[1].blue + au8[2] * pixel[2].blue + au8[3] * pixel[3].blue;
        sA += aA * av8[i];
        sR += aR * av8[i];
        sG += aG * av8[i];
        sB += aB * av8[i];
        pPixel += lpbBitmap->bmWidthBytes;
    }

    xARGB clr;
    clr.alpha = ColorTable[sA>>16];
    clr.red = ColorTable[sR>>16];
    clr.green = ColorTable[sG>>16];
    clr.blue = ColorTable[sB>>16];
#ifdef BLENDALPHA
    MulAlpha(clr, alpha);
#endif
    BLENDPIXEL(result, clr);
}

#ifndef BLENDALPHA
__forceinline void BLENDBORDERSTRETCH(const PBITMAPEX lpbBitmap, const int x16, const int y16, LPXARGB result)
#else
__forceinline void BLENDBORDERSTRETCH(const PBITMAPEX lpbBitmap, const int x16, const int y16, LPXARGB result, DWORD alpha)
#endif
{
    int x0Sub = (x16 >> 16) - 1;
    int y0Sub = (y16 >> 16) - 1;
    DWORD u16 = ((WORD)(x16)) + (1 << 16);
    DWORD v16 = ((WORD)(y16)) + (1 << 16);

    PIXEL pixel[16];
    for (int i = 0; i < 4; ++i)
    {
        int y = min(max(y0Sub + i, 0), lpbBitmap->bmHeight - 1);
        int x0 = min(max(x0Sub + 0, 0), lpbBitmap->bmWidth - 1);
        int x1 = min(max(x0Sub + 1, 0), lpbBitmap->bmWidth - 1);
        int x2 = min(max(x0Sub + 2, 0), lpbBitmap->bmWidth - 1);
        int x3 = min(max(x0Sub + 3, 0), lpbBitmap->bmWidth - 1);
        pixel[i*4+0] = *((PIXEL*)((LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * y) + x0);
        pixel[i*4+1] = *((PIXEL*)((LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * y) + x1);
        pixel[i*4+2] = *((PIXEL*)((LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * y) + x2);
        pixel[i*4+3] = *((PIXEL*)((LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * y) + x3);
    }

    BITMAPEX bmp;
    bmp.bmBits = &pixel[0];
    bmp.bmWidthBytes = 4 * PIXEL_BYTES;
#if PIXEL_BITS == 8
    bmp.bmClrUsed = lpbBitmap->bmClrUsed;
    bmp.bmColors = lpbBitmap->bmColors;
#endif
#ifndef BLENDALPHA
    BLENDPIXELSTRETCH(&bmp, u16, v16, result);
#else
    BLENDPIXELSTRETCH(&bmp, u16, v16, result, alpha);
#endif
}

#ifndef BLENDALPHA
BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc)
#else
BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc, BYTE alpha)
#endif
{
    DWORD xrIntFloat16 = (lpbSrc->bmWidth << 16) / lpbDst->bmWidth + 1;
    DWORD yrIntFloat16 = (lpbSrc->bmHeight << 16) / lpbDst->bmHeight + 1;

    const int csX = -(1 << 15) + ((int)xrIntFloat16 >> 1);
    const int csY = -(1 << 15) + ((int)yrIntFloat16 >> 1);

    int dstWidth = lpbDst->bmWidth;

    int borderY1 = ((1 << 16) - csY) / (int)yrIntFloat16 + 1;
    if (borderY1 >= lpbDst->bmHeight)
        borderY1 = lpbDst->bmHeight;
    int borderX1 = ((1 << 16) - csX) / (int)xrIntFloat16 + 1;
    if (borderX1 >= lpbDst->bmWidth)
        borderX1 = lpbDst->bmWidth;
    int borderY2 = (((lpbSrc->bmHeight - 3) << 16) - csY) / (int)yrIntFloat16 + 1;
    if (borderY2 < borderY1)
        borderY2 = borderY1;
    int borderX2 = (((lpbSrc->bmWidth - 3) << 16) - csX) / (int)xrIntFloat16 + 1; 
    if (borderX2 < borderX1)
        borderX2 = borderX1;

    LPXARGB pDstLine = (LPXARGB)lpbDst->bmBits;
    int srcy16 = csY;

#ifdef BLENDALPHA
    DWORD dwAlpha = (DWORD)alpha + 1;
#endif

    for (int y = 0; y < borderY1; ++y)
    {
        int srcx16 = csX;
        for (int x = 0; x < dstWidth; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        srcy16 += yrIntFloat16;
        ((BYTE*&)pDstLine) += lpbDst->bmWidthBytes;
    }

    for (int y = borderY1; y < borderY2; ++y)
    {
        LPXARGB pDstLine = (LPXARGB)((LPBYTE)lpbDst->bmBits + y * lpbDst->bmWidthBytes);
        int srcy16 = csY + y * yrIntFloat16;
        int srcx16 = csX;
        for (int x = 0; x < borderX1; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        for (int x = borderX1; x < borderX2; ++x)
        {
#ifndef BLENDALPHA
            BLENDPIXELSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDPIXELSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        for (int x = borderX2; x < dstWidth; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
    }
    pDstLine = (LPXARGB)((LPBYTE)lpbDst->bmBits + borderY2 * lpbDst->bmWidthBytes);
    srcy16 = csY + borderY2 * yrIntFloat16;
    for (int y = borderY2; y < lpbDst->bmHeight; ++y)
    {
        int srcx16 = csX;
        for (int x = 0; x < dstWidth; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        srcy16 += yrIntFloat16;
        ((BYTE*&)pDstLine) += lpbDst->bmWidthBytes;
    }
    return TRUE;
}

#ifndef BLENDALPHA
BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc, LPCSIZE lpszDst, LPCRECT lprcClipped)
#else
BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc, BYTE alpha, LPCSIZE lpszDst, LPCRECT lprcClipped)
#endif
{
    DWORD xrIntFloat16 = (lpbSrc->bmWidth << 16) / lpszDst->cx + 1;
    DWORD yrIntFloat16 = (lpbSrc->bmHeight << 16) / lpszDst->cy + 1;

    const int csX = -(1 << 15) + ((int)xrIntFloat16 >> 1);
    const int csY = -(1 << 15) + ((int)yrIntFloat16 >> 1);

    int borderY1 = ((1 << 16) - csY) / (int)yrIntFloat16 + 1;
    if (borderY1 >= lpszDst->cy)
        borderY1 = lpszDst->cy;
    int borderX1 = ((1 << 16) - csX) / (int)xrIntFloat16 + 1;
    if (borderX1 >= lpszDst->cx)
        borderX1 = lpszDst->cx;
    int borderY2 = (((lpbSrc->bmHeight - 3) << 16) - csY) / (int)yrIntFloat16 + 1;
    if (borderY2 < borderY1)
        borderY2 = borderY1;
    int borderX2 = (((lpbSrc->bmWidth - 3) << 16) - csX) / (int)xrIntFloat16 + 1; 
    if (borderX2 < borderX1)
        borderX2 = borderX1;

    DWORD dstByteWidth = lpbDst->bmWidthBytes;
    LPXARGB pDstLine = (LPXARGB)lpbDst->bmBits;
    int srcy16 = csY + lprcClipped->top * yrIntFloat16;
    int srcxOriginal16 = csX + lprcClipped->left * xrIntFloat16;

    if (borderX1 > lprcClipped->right) borderX1 = lprcClipped->right;
    if (borderX2 > lprcClipped->right) borderX2 = lprcClipped->right;
    if (borderY1 > lprcClipped->bottom) borderY1 = lprcClipped->bottom;
    if (borderY2 > lprcClipped->bottom) borderY2 = lprcClipped->bottom;
    if (borderX1 < lprcClipped->left) borderX1 = lprcClipped->left;
    if (borderX2 < lprcClipped->left) borderX2 = lprcClipped->left;
    if (borderY1 < lprcClipped->top) borderY1 = lprcClipped->top;
    if (borderY2 < lprcClipped->top) borderY2 = lprcClipped->top;

#ifdef BLENDALPHA
    DWORD dwAlpha = (DWORD)alpha + 1;
#endif

    for (int y = lprcClipped->top; y < borderY1; ++y)
    {
        int srcx16 = srcxOriginal16;
        for (int x = lprcClipped->left; x < lprcClipped->right; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        srcy16 += yrIntFloat16;
        ((LPBYTE&)pDstLine) += dstByteWidth;
    }

    for (int y = borderY1; y < borderY2; ++y)
    {
        LPXARGB pDstLine = (LPXARGB)((LPBYTE)lpbDst->bmBits + (y - lprcClipped->top) * lpbDst->bmWidthBytes);
        int srcy16 = csY + y * yrIntFloat16;
        int srcx16 = srcxOriginal16;
        for (int x = lprcClipped->left; x < borderX1; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        for (int x = borderX1; x < borderX2; ++x)
        {
#ifndef BLENDALPHA
            BLENDPIXELSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDPIXELSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        for (int x = borderX2; x < lprcClipped->right; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
    }
    pDstLine = (LPXARGB)((LPBYTE)lpbDst->bmBits + (borderY2 - lprcClipped->top) * lpbDst->bmWidthBytes);
    srcy16 = csY + borderY2 * yrIntFloat16;
    for (int y = borderY2; y < lprcClipped->bottom; ++y)
    {
        int srcx16 = srcxOriginal16;
        for (int x = lprcClipped->left; x < lprcClipped->right; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], dwAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        srcy16 += yrIntFloat16;
        ((LPBYTE&)pDstLine) += dstByteWidth;
    }

    return TRUE;
}